package com.leon.mybatis;

import com.leon.mybatis.mapper.UserMapper;
import com.leon.mybatis.model.entity.User;
import org.apache.ibatis.annotations.Select;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 史鸿禹
 * @date 2022/07/15  10:55 上午
 */
public class Main {
    /**
     * 使用JDK动态代理
     * 目的： 我们现在要使用mapper接口的方法去调用mybatis获取到数据库数据
     * 问题1：但是不能直接创建接口的对象，所以我们需要代理一个对象，然后对这个代理对象操作
     * @param args
     */
    public static void main(String[] args) {
//                                                               1。 获取到当前类的类加载            2。需要被使用的接口字节码集合    3。拦截处理器，我们的增强功能在此实现
        UserMapper mapper = (UserMapper) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
//                            4/proxy一般用不到 5/这个method是在被调用时传入的方法  6/args时调用方法的参数
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //验证 56点
                System.out.println("method---"+method.getName());
                System.out.println("args---"+Arrays.asList(args));
                //现在要干什么？
                //目的2：获取sql语句
                //获取到我们方法上的select语句
                Select selectSql = method.getAnnotation(Select.class);
                //打印sql语句看我们获取到了么
                System.out.println(selectSql);
                //目的3：把我们的参数填充到sql中，写个方法
                Map<String, Object> params = getParams(method, args);
                //获取到了参数了，我们就需要把sql中的占位符#{}转换成我们的参数
                String finalSql = parseSql(selectSql.value()[0], params);
                System.out.println(finalSql);
                //之后就是jdbc
                //最后封装成实体类，mybatis就做了这几步
                return null;
            }
        });
        //                        这里mapper调用的方法就是上面的5/method 参数就是6/args
        List<User> users = mapper.selectUsers("leon", 13);
    }

    /**
     * 解析sql   方法比较暴力，就是循环获取字符串，获取到#{}里面的内容去map里找到对应的值，然后塞回去
     * @param sql
     * @param params
     * @return
     */
    private static String parseSql(String sql, Map<String, Object> params) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < sql.length() ; i++) {
            char c = sql.charAt(i);
            if (c == '#'){
                if (sql.charAt(++i) == '{'){
                    i = insertParam(sql, params, i, sb);
                }
                    }else{
                sb.append(c);

            }
                }

        return sb.toString();
    }

    /**
     * 值替换
     * @param sql  源sql
     * @param params   参数map
     * @param i         当前索引位置
     * @param sb      待拼接的字符串
     * @return
     */
    private static int insertParam(String sql, Map<String, Object> params, int i,StringBuilder sb) {
        int index = ++i;
        //从{下一位开始所以先++
        for (;;i++){
            char c = sql.charAt(i);
            if (c == '}'){
                String key = sql.substring(index, i);
                Object param = params.get(key);
                sb.append(param);
                break;
            }
        }
        return i;
    }

    public static Map<String,Object> getParams(Method method,Object[] args){
        Map<String, Object> resultMap = new HashMap<>();
        //获取到参数名称与我们的参数绑定
        Parameter[] parameters = method.getParameters();
        //问题2：lamda表达式中的参数必须是final类型怎么循环？
        //解决2：可以改成正常的for方式写，也可以把这个index放到数组的第一个位置然后操作第一个位置
        //int[] index = 0;
        //        Arrays.stream(parameters).forEach(
        //                v ->{
        //                    resultMap.put(v.getName(), (String) Arrays.asList(args).get(index));
        //                    index++;
        //                }
        //
        //        );
        //=====================================================================把上面的代码放开就可以看到问题
        int[] index = {0};
        Arrays.stream(parameters).forEach(
                v ->{
                    resultMap.put(v.getName(),args[index[0]]);
                    index[0]++;
                }
        );
        System.out.println(resultMap);
        //{arg1=13, arg0=leon} 会发现这里打印的有问题
        //解决方法：https://www.yuque.com/docs/share/fce6e116-d1f7-4a9e-a6fe-750d8d0ada3b?# 《Mybatis源码》
        return resultMap;
    }
}
